## Copyright 2015-2019 Ilgar Lunin, Pedro Cabrera

## Licensed under the Apache License, Version 2.0 (the "License");
## you may not use this file except in compliance with the License.
## You may obtain a copy of the License at

##     http://www.apache.org/licenses/LICENSE-2.0

## Unless required by applicable law or agreed to in writing, software
## distributed under the License is distributed on an "AS IS" BASIS,
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
## See the License for the specific language governing permissions and
## limitations under the License.


import os
import platform
from copy import copy, deepcopy
from PyFlow.Core import FunctionLibraryBase, IMPLEMENT_NODE
from PyFlow import getHashableDataTypes
from PyFlow.Core.Common import *
from PyFlow.Core.PathsRegistry import PathsRegistry


PIN_ALLOWS_ANYTHING = {
    PinSpecifiers.ENABLED_OPTIONS: PinOptions.AllowAny
                                   | PinOptions.ArraySupported
                                   | PinOptions.DictSupported
}


class DefaultLib(FunctionLibraryBase):
    """Default library building stuff, variable types and conversions
    """

    def __init__(self, packageName):
        super(DefaultLib, self).__init__(packageName)

    @staticmethod
    @IMPLEMENT_NODE(
        returns=(
            "AnyPin",
            None,
            {
                PinSpecifiers.ENABLED_OPTIONS: PinOptions.ArraySupported
                                               | PinOptions.AllowAny
                                               | PinOptions.DictElementSupported,
                PinSpecifiers.CONSTRAINT: "1",
                PinSpecifiers.STRUCT_CONSTRAINT: "1",
            },
        ),
        meta={NodeMeta.CATEGORY: "Utils", NodeMeta.KEYWORDS: ["id"]},
    )
    def copyObject(
        obj=(
            "AnyPin",
            None,
            {
                PinSpecifiers.ENABLED_OPTIONS: PinOptions.ArraySupported
                                               | PinOptions.AllowAny
                                               | PinOptions.DictElementSupported,
                PinSpecifiers.CONSTRAINT: "1",
                PinSpecifiers.STRUCT_CONSTRAINT: "1",
            },
        ),
        deepCopy=("BoolPin", False),
    ):
        """Shallow or deep copy of an object."""
        copyFunction = deepcopy if deepCopy else copy
        return copyFunction(obj)

    @staticmethod
    @IMPLEMENT_NODE(
        returns=None,
        nodeType=NodeTypes.Callable,
        meta={NodeMeta.CATEGORY: "Common", NodeMeta.KEYWORDS: []},
    )
    def clearConsole():
        """Cross-platform clears console."""
        system = platform.system()
        if system != "":
            system = system.lower()
            if system in ("windows", "win32"):
                os.system("cls")
            if system in ("linux", "darwin", "linux2"):
                os.system("clear")

    @staticmethod
    @IMPLEMENT_NODE(
        returns=("IntPin", 0),
        meta={NodeMeta.CATEGORY: "GenericTypes", NodeMeta.KEYWORDS: []},
    )
    def makeInt(i=("IntPin", 0)):
        """Make integer."""
        return i

    @staticmethod
    @IMPLEMENT_NODE(
        returns=("FloatPin", 0.0),
        meta={NodeMeta.CATEGORY: "GenericTypes", NodeMeta.KEYWORDS: []},
    )
    def makeFloat(f=("FloatPin", 0.0)):
        """Make floating point number."""
        return f

    @staticmethod
    @IMPLEMENT_NODE(
        returns=("StringPin", ""),
        meta={NodeMeta.CATEGORY: "GenericTypes", NodeMeta.KEYWORDS: []},
    )
    def makeString(s=("StringPin", "")):
        """Make string."""
        return s

    @staticmethod
    @IMPLEMENT_NODE(
        returns=None,
        nodeType=NodeTypes.Callable,
        meta={NodeMeta.CATEGORY: "Common", NodeMeta.KEYWORDS: []},
    )
    def setGlobalVar(
        name=("StringPin", "var1"), value=("AnyPin", None, PIN_ALLOWS_ANYTHING.copy())
    ):
        """Sets value to globals() dict"""
        globals()[name] = value

    @staticmethod
    @IMPLEMENT_NODE(
        returns=("AnyPin", None, PIN_ALLOWS_ANYTHING.copy()),
        meta={NodeMeta.CATEGORY: "Common", NodeMeta.KEYWORDS: []},
    )
    def getAttribute(
        obj=("AnyPin", None, PIN_ALLOWS_ANYTHING.copy()), name=("StringPin", "attrName")
    ):
        """Returns attribute from object using "getattr(name)\""""
        return getattr(obj, name)

    @staticmethod
    @IMPLEMENT_NODE(
        returns=("AnyPin", None, PIN_ALLOWS_ANYTHING.copy()),
        meta={
            NodeMeta.CATEGORY: "Common",
            NodeMeta.KEYWORDS: [],
            NodeMeta.CACHE_ENABLED: False,
        },
    )
    def getGlobalVar(name=("StringPin", "var1")):
        """Retrieves value from globals()"""
        if name in globals():
            return globals()[name]
        else:
            return None

    @staticmethod
    @IMPLEMENT_NODE(
        returns=("StringPin", ""),
        meta={NodeMeta.CATEGORY: "GenericTypes", NodeMeta.KEYWORDS: []},
    )
    def makePath(
        path=("StringPin", "", {PinSpecifiers.INPUT_WIDGET_VARIANT: "PathWidget"})
    ):
        """Make path."""
        return path

    @staticmethod
    @IMPLEMENT_NODE(
        returns=("BoolPin", False),
        meta={NodeMeta.CATEGORY: "GenericTypes", NodeMeta.KEYWORDS: []},
    )
    def makeBool(b=("BoolPin", False)):
        """Make boolean."""
        return b

    @staticmethod
    @IMPLEMENT_NODE(
        returns=(
            "FloatPin",
            0.0,
            {PinSpecifiers.ENABLED_OPTIONS: PinOptions.AlwaysPushDirty},
        ),
        meta={
            NodeMeta.CATEGORY: "Utils",
            NodeMeta.KEYWORDS: [],
            NodeMeta.CACHE_ENABLED: False,
        },
    )
    def clock():
        """Returns the CPU time or real time since the start of the process or since the first call of process_time()."""
        return currentProcessorTime()

    @staticmethod
    @IMPLEMENT_NODE(
        returns=(
            "AnyPin",
            None,
            {
                PinSpecifiers.CONSTRAINT: "1",
                PinSpecifiers.ENABLED_OPTIONS: PinOptions.ArraySupported
                                               | PinOptions.AllowAny
                                               | PinOptions.DictElementSupported,
            },
        ),
        meta={NodeMeta.CATEGORY: "DefaultLib", NodeMeta.KEYWORDS: []},
    )
    def select(
        A=(
            "AnyPin",
            None,
            {
                PinSpecifiers.CONSTRAINT: "1",
                PinSpecifiers.ENABLED_OPTIONS: PinOptions.ArraySupported
                                               | PinOptions.AllowAny
                                               | PinOptions.DictElementSupported,
            },
        ),
        B=(
            "AnyPin",
            None,
            {
                PinSpecifiers.CONSTRAINT: "1",
                PinSpecifiers.ENABLED_OPTIONS: PinOptions.ArraySupported
                                               | PinOptions.AllowAny
                                               | PinOptions.DictElementSupported,
            },
        ),
        PickA=("BoolPin", False),
        aPicked=(REF, ("BoolPin", False)),
    ):
        """
        If bPickA is true, A is returned, otherwise B.
        """
        aPicked(PickA)
        return A if PickA else B

    @staticmethod
    @IMPLEMENT_NODE(
        returns=("StringPin", ""),
        meta={NodeMeta.CATEGORY: "GenericTypes", NodeMeta.KEYWORDS: []},
    )
    def objectType(
        obj=(
            "AnyPin",
            None,
            {
                PinSpecifiers.ENABLED_OPTIONS: PinOptions.ArraySupported
                                               | PinOptions.AllowAny
                                               | PinOptions.DictElementSupported
            },
        )
    ):
        """Returns ``type(obj).__name__``"""
        t = type(obj).__name__
        if t == "DictElement":
            t += ",key:{0},value:{1}".format(
                type(obj[1]).__name__, type(obj[0]).__name__
            )
        return t

    @staticmethod
    @IMPLEMENT_NODE(
        returns=("BoolPin", False),
        meta={NodeMeta.CATEGORY: "DefaultLib", NodeMeta.KEYWORDS: ["in"]},
    )
    def contains(
        obj=(
            "AnyPin",
            None,
            {
                PinSpecifiers.CONSTRAINT: "1",
                PinSpecifiers.ENABLED_OPTIONS: PinOptions.ArraySupported
                                               | PinOptions.AllowAny
                                               | PinOptions.DictElementSupported,
            },
        ),
        element=("AnyPin", None, {PinSpecifiers.CONSTRAINT: "1"}),
    ):
        """Python's **in** keyword. `element in obj` will be executed"""
        try:
            return element in obj
        except:
            return False

    @staticmethod
    @IMPLEMENT_NODE(
        returns=(
            "IntPin",
            0,
            {PinSpecifiers.DESCRIPTION: "Number of elements of iterable"},
        ),
        meta={NodeMeta.CATEGORY: "DefaultLib", NodeMeta.KEYWORDS: ["len"]},
    )
    def len(
        obj=(
            "AnyPin",
            None,
            {
                PinSpecifiers.ENABLED_OPTIONS: PinOptions.ArraySupported
                                               | PinOptions.AllowAny
                                               | PinOptions.DictElementSupported,
                PinSpecifiers.DESCRIPTION: "Iterable object",
            },
        )
    ):
        """Python's **len** function."""
        try:
            return len(obj)
        except:
            return -1

    @staticmethod
    @IMPLEMENT_NODE(
        returns=(
            "AnyPin",
            None,
            {
                PinSpecifiers.CONSTRAINT: "1",
                PinSpecifiers.ENABLED_OPTIONS: PinOptions.ArraySupported
                                               | PinOptions.AllowAny,
            },
        ),
        meta={NodeMeta.CATEGORY: "DefaultLib", NodeMeta.KEYWORDS: ["get"]},
    )
    def getItem(
        obj=(
            "AnyPin",
            None,
            {
                PinSpecifiers.CONSTRAINT: "1",
                PinSpecifiers.ENABLED_OPTIONS: PinOptions.ArraySupported
                                               | PinOptions.AllowAny,
            },
        ),
        element=(
            "AnyPin",
            None,
            {PinSpecifiers.SUPPORTED_DATA_TYPES: getHashableDataTypes()},
        ),
        result=(REF, ("BoolPin", False)),
    ):
        """Python's ``[]`` operator. ``obj[element]`` will be executed."""
        try:
            result(True)
            return obj[element]
        except:
            result(False)
            return None

    @staticmethod
    @IMPLEMENT_NODE(
        returns=(
            "AnyPin",
            None,
            {
                PinSpecifiers.CONSTRAINT: "1",
                PinSpecifiers.STRUCT_CONSTRAINT: "1",
                PinSpecifiers.ENABLED_OPTIONS: PinOptions.ArraySupported
                                               | PinOptions.AllowAny,
            },
        ),
        meta={NodeMeta.CATEGORY: "DefaultLib", NodeMeta.KEYWORDS: ["get"]},
    )
    def appendTo(
        obj=(
            "AnyPin",
            None,
            {
                PinSpecifiers.CONSTRAINT: "1",
                PinSpecifiers.STRUCT_CONSTRAINT: "1",
                PinSpecifiers.ENABLED_OPTIONS: PinOptions.ArraySupported
                                               | PinOptions.AllowAny,
            },
        ),
        element=("AnyPin", None, {PinSpecifiers.CONSTRAINT: "1"}),
        result=(REF, ("BoolPin", False)),
    ):
        """Calls ``obj.append(element)``. And returns object. If failed - object is unchanged"""
        try:
            obj.append(element)
            result(True)
            return obj
        except:
            result(False)
            return obj

    @staticmethod
    @IMPLEMENT_NODE(
        returns=("BoolPin", False),
        meta={NodeMeta.CATEGORY: "DefaultLib", NodeMeta.KEYWORDS: ["get"]},
    )
    def addTo(
        obj=(
            "AnyPin",
            None,
            {
                PinSpecifiers.CONSTRAINT: "1",
                PinSpecifiers.STRUCT_CONSTRAINT: "1",
                PinSpecifiers.ENABLED_OPTIONS: PinOptions.ArraySupported
                                               | PinOptions.AllowAny,
            },
        ),
        element=("AnyPin", None, {PinSpecifiers.CONSTRAINT: "1"}),
        result=(
            REF,
            (
                "AnyPin",
                None,
                {
                    PinSpecifiers.CONSTRAINT: "1",
                    PinSpecifiers.STRUCT_CONSTRAINT: "1",
                    PinSpecifiers.ENABLED_OPTIONS: PinOptions.ArraySupported
                                                   | PinOptions.AllowAny,
                },
            ),
        ),
    ):
        """Calls ``obj.add(element)``. And returns object. If failed - object is unchanged"""
        try:
            obj.add(element)
            result(obj)
            return True
        except:
            result(obj)
            return False
